﻿using System;
using System.Threading;

namespace Portkit.Core
{
    /// <summary>
    /// Represents a synchronization context dispatcher.
    /// </summary>
    public class PortableDispatcher
    {
        private static SynchronizationContext _context;
        private static volatile PortableDispatcher _default;
        private static readonly object _syncRoot = new Object();

        /// <summary>
        /// Gets the default dispatcher.
        /// </summary>
        public static PortableDispatcher Default
        {
            get
            {
                if (_default == null)
                {
                    lock (_syncRoot)
                    {
                        if (_default == null)
                        {
                            _default = new PortableDispatcher();
                        }
                    }
                }
                return _default;
            }
        }

        /// <summary>
        /// Sets the PortableDispatcher synchronization context to the thread's current synchronization context.
        /// </summary>
        public void Attach()
        {
            Attach(SynchronizationContext.Current);
        }

        /// <summary>
        /// Sets the PortableDispatcher synchronization context.
        /// </summary>
        /// <param name="context">Synchronization context.</param>
        public void Attach(SynchronizationContext context)
        {
            _context = context;
            if (_context == null)
            {
                throw new InvalidOperationException("Unable to find a suitable SynchronizationContext instance.");
            }
        }

        /// <summary>
        /// Determines whether the calling thread is the thread associated with this PortableDispatcher.
        /// </summary>
        /// <returns>true if the calling thread is the thread associated with this PortableDispatcher; otherwise, false.
        /// </returns>
        public bool CheckAccess()
        {
            if (_context == null)
            {
                Attach();
            }
            return SynchronizationContext.Current == _context;
        }

        /// <summary>
        /// Dispatches an action for execution to the attached thread's synchronization context.
        /// </summary>
        /// <param name="actionDelegate">Message to be posted.</param>
        /// <param name="state">The object passed to the delegate.</param>
        public void Post(Action actionDelegate, object state)
        {
            if (CheckAccess())
            {
                actionDelegate();
            }
            else
            {
                try
                {
                    if (_context != null)
                    {
                        _context.Post(callback => actionDelegate(), state);
                    }
                    else
                    {
                        actionDelegate();
                    }
                }
                catch (Exception e)
                {
                    const string msg = "Unable to invoke action delegate. " +
                        "Most likely the dispatcher is not attached to the correct SynchronizationContext. " +
                        "Check inner exception for more details.";
                    throw new InvalidOperationException(msg, e);
                }
            }
        }

        /// <summary>
        /// Dispatches an action for execution to the attached thread's synchronization context.
        /// </summary>
        /// <param name="actionDelegate">Message to be posted.</param>
        public void Post(Action actionDelegate)
        {
            Post(actionDelegate, null);
        }
    }
}
